﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace MetalSoda
{
    /// <summary>
    /// SSB Service
    /// </summary>
    [DebuggerDisplay("Service: {Name}, Queue: {Queue.Name}, Contracts: {CountractCount}")]
    public class Service : IDdlObject
    {
        /// <summary>
        /// Service name
        /// </summary>
        public readonly string Name;

        #region Private Fields
        readonly OneToOneAssociation<Service, Queue> m_queueAssociation;
        readonly OneToManyAssociation<Service, Contract> m_contractAssociations;
        readonly OneToManyAssociation<Service, ServiceHost> m_hostAssociations; 
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="MetalSoda.Service"/> class.
        /// </summary>
        /// <param name="name">Service name</param>
        public Service(string name)
        {
            Name = name;
            m_queueAssociation = new OneToOneAssociation<Service, Queue>(this,
                assigned: args => args.Ref.AddService(this),
                unassigned: args => args.Ref.RemoveService(this));
            m_contractAssociations = new OneToManyAssociation<Service,Contract>(this,
                added: args => args.Ref.AddService(this),
                removed: args => args.Ref.RemoveService(this));
            m_hostAssociations = new OneToManyAssociation<Service,ServiceHost>(this,
                added: args => args.Ref.AddService(this),
                removed: args=> args.Ref.RemoveService(this));
        }

        #region Properties
        /// <summary>
        /// Gets or sets the queue to be targeted by this service
        /// </summary>
        public Queue Queue
        {
            get { return m_queueAssociation.Ref; }
            set { m_queueAssociation.Ref = value; }
        }

        /// <summary>
        /// Gets contracts implemented by this service.
        /// </summary>
        public IEnumerable<Contract> Contracts { get { return m_contractAssociations.AsEnumerable(); } }

        /// <summary>
        /// Gets messages that can be sent or received by this service.
        /// </summary>
        public IEnumerable<MessageType> MessageTypes { get { return Contracts.SelectMany(c => c.MessageTypes).Distinct(); } }

        /// <summary>
        /// Gets total number of contracts implemented by this service.
        /// </summary>
        public int CountractCount { get { return m_contractAssociations.Count; } } 
        #endregion

        #region Methods
        /// <summary>
        /// Adds the specified contract to contracts implemented by this service
        /// </summary>
        /// <param name="contract">Contract to be implemented by this service.</param>
        /// <returns>Self</returns>
        public Service AddContract(Contract contract)
        {
            if (Contract(contract.Name) != null && Contract(contract.Name) != contract)
            {
                var msg = string.Format("The {0} contract already exists.", contract.Name);
                throw new InvalidOperationException(msg);
            }
            m_contractAssociations.Add(contract);
            return this;
        }

        /// <summary>
        /// Removes the specified contract from contracts implemented by this service
        /// </summary>
        /// <param name="contract">Contract</param>
        /// <returns>Self</returns>
        public Service RemoveContract(Contract contract)
        {
            if (Object.ReferenceEquals(contract, null)) { return this; }
            m_contractAssociations.Remove(Contract(contract.Name));
            return this;
        }

        /// <summary>
        /// Finds contract implemented by this service by the contracts name.
        /// </summary>
        /// <param name="contractName">Contract name</param>
        /// <returns>Contract instance</returns>
        public Contract Contract(string contractName)
        {
            return m_contractAssociations.Where(c => Utils.AreSame(c.Name, contractName)).SingleOrDefault();
        }

        /// <summary>
        /// Registers the specified service host object to host this service.
        /// </summary>
        /// <param name="host">Service host</param>
        /// <returns>Self</returns>
        public Service AddHost(ServiceHost host)
        {
            if (Host(host.Name) != null)
            {
                if (Host(host.Name) == host) { return this; }
                throw new InvalidOperationException();
            }
            m_hostAssociations.Add(host);
            return this;
        }

        /// <summary>
        /// Unregisters the specified service host object so that it will not host this service.
        /// </summary>
        /// <param name="host">Service host</param>
        /// <returns>Self</returns>
        public Service RemoveHost(ServiceHost host)
        {
            if (Object.ReferenceEquals(host, null)) { return this; }
            m_hostAssociations.Remove(Host(host.Name));
            return this;
        }

        /// <summary>
        /// Finds the service host object hosting this service by the host name.
        /// </summary>
        /// <param name="name">Host name</param>
        /// <returns>Service host</returns>
        public ServiceHost Host(string name)
        {
            return m_hostAssociations.Where(h => Utils.AreSame(h.Name, name)).SingleOrDefault();
        } 
        #endregion

        #region IDdlObject Implementation

        /// <summary>
        /// Generates a sequence on the CREATE batches for this service.
        /// </summary>
        /// <returns>T-SQL batches</returns>
        public IEnumerable<string> GenerateCreateBatches()
        {
            yield return new SQLScript.CreateServiceRuntimeTextTemplate(this).TransformText();
        }

        /// <summary>
        /// Generates a sequence on the ALTER batches for this service.
        /// </summary>
        /// <returns>T-SQL batches</returns>
        public IEnumerable<string> GenerateAlterBatches()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Generates a sequence on the DELETE batches for this service.
        /// </summary>
        /// <returns>T-SQL batches</returns>
        public IEnumerable<string> GenerateDeleteBatches()
        {
            yield return string.Format(@"
                    if exists(select * from sys.services as s where s.name = '{0}')
		                drop service [{0}];
            ", this.Name);
        } 
        #endregion
    }
}
